成品連結:Whack A Mole、完成前程式碼、完成後程式碼
今天要做的是打地鼠的遊戲!實作之前覺得很難,但在實作之後發現其實沒有想像中困難,一起來做做看吧!
與之前一樣,我們先列出要做的事情吧!
預計做出的成品要有下列功能:
從功能我們可以列出下面的事項:
列完了就開始吧!
這裡先解釋一下地數出現的方式,在 CSS 中有先寫好一個 up
的 class,當將它加到 div.hole
時,地數就會出現。
這裡要新增一個 function 並帶入一個引數 holes
也就是所有的地洞
function randomHole(holes) {
// code here
}
接著要隨機產生 0~5 的數字當作是 holes
的 index,這樣我們就能選到隨機的一個地洞
function randomHole(holes) {
const index = Math.floor(Math.random() * holes.length);
const hole = holes[index];
return hole;
}
這樣會有功能(可以試著在 console 執行看看!),但是由於是隨機產生數字,所以有可能連續 2 次、 3 次,甚至更多次連續選到同一個地洞。為了避免這種情形,我們要先記錄上一次選到的地洞,如果這次又選到了,就重新執行 randomHole
直到選到不一樣的動為止。
沒錯!你可能已經想到了,我們會使用迴圈(recursion)。
let lastHole; // 記錄上次的地洞
function randomHole(holes) {
const index = Math.floor(Math.random() * holes.length); // 會是 0 ~ 5 的數字
const hole = holes[index];
if (lastHole === hole) {
return randomHole(holes);
}
lastHole = hole;
return hole;
}
使用迴圈的基本方法是會設定 if...else
做判斷,若不是預期的結果則重新呼叫自己(return 該 function);若是預期結果則 return 該值並跳出迴圈。
在我們的例子中,當 hole
與上次相同實則重新產生新的 hole
,若與上次不一樣則 return 該值。
這裡我們同樣要隨機產生時間,而 function 的引數會有兩個,max
與 min
,分別代表時間的最大值與最小值。
function randomTime(min, max) {
const time = Math.round(Math.random() * (max - min) + min);
return time;
}
上面這個寫法之前我們已經看過了,能夠幫我們得到 min
到 max
之間的數字;而 Math.round
幫我們四捨五入並取整數
地鼠已經可以隨機出現(但尚未渲染至畫面)、隨機出現的時間也得到了,我們來讓地鼠出現吧!
首先先使用剛剛建立的兩個 function randomHole
、randomTime
取得地洞與出現的時間
function peep() {
const time = randomTime(300, 1000); // 最小 300 毫秒、最大 1000 毫秒
const hole = randomHole(holes);
}
要讓地鼠出現,需要將 class up
加到 hole
上面
function peep() {
const time = randomTime(300, 1000); // 最小 300 毫秒、最大 1000 毫秒
const hole = randomHole(holes);
hole.classList.add('up');
}
但是這樣只會出現一次地鼠,為了要不停的出現直到遊戲時間結束,我們要再使用迴圈的方式,但是直接在 peep
中呼叫自己會造成無限迴圈(因為沒有跳出的條件)。我們希望停止執行 peep
的條件是當遊戲時間結束時,因此我們需要建立一個變數(也就是之前提到的 flag)來記錄時間是否已到。
let timeUp = false; // 當遊戲時間結束時會改為 true
function peep() {
const time = randomTime(300, 1000); // 最小 300 毫秒、最大 1000 毫秒
const hole = randomHole(holes);
hole.classList.add('up');
if (!timeUp) {
peep();
}
}
這樣地數會出現了,但不會消失啊...這時 time
派上用場了,我們使用 setTimeout
來設定結束時從 hole
移除 up
這個 class
let timeUp = false; // 當遊戲時間結束時會改為 true
function peep() {
const time = randomTime(300, 1000); // 最小 300 毫秒、最大 1000 毫秒
const hole = randomHole(holes);
hole.classList.add('up');
setTimeout(() => {
hole.classList.remove('up');
if (!timeUp) {
peep();
}
}, time);
}
也就是當第一個地鼠出現時,在過了 time
的時間後移除 class up
並檢查遊戲是否已結束,若還沒則再度執行 peep
在 HTML 的按鈕已經有綁定 click
事件,所以當按下按鈕後會開始遊戲,或是遊戲當中按下時會重置並重新開始。
在這個 function 中我們要執行 peep
並設定遊戲結束時間,並在結束時將 timeUp
設為 ture
,以讓 peep
不再執行
function startGame() {
scoreBoard.textContent = '0'; // 重設分數
timeUp = false;
peep();
setTimeout(() => {
timeUp = true; // 當 timeUp === true 時,peep 會停止執行
}, 10000) // 設定 10 秒後遊戲結束
}
最後當使用者打到地鼠時,需要更新分數並從 hole
移除 class up
function scoreUpdate(e) {
let score = Number(scoreBoard.textContent); // 取得當前分數並轉型為 number
if (!e.isTrusted) return; // 確保使用者真的用滑鼠點擊
score++;
scoreBoard.textContent = score;
this.classList.remove('up');
}
到這裡就完成啦!
好不容易完成 30 天的挑戰了,想當初看到這 30 天的成品時從沒認為自己有能力完成;而是既操作時雖然有幾天還是需要看影片才能完成,但很高興自己能夠獨立完成大部分的天數。
這 30 天從作者(Wes Bos)學到了不少觀念像是寫程式的思考邏輯、如何將大問題拆解成小問題再各個擊破等等...。重點是 Wes 的 CSS 手法真的很厲害,有幾天我甚至覺得他的 CSS 比 JS 更值得學習啊(尤其是第 27 天)!
總之,非常謝謝你(妳)的觀看,希望在這 30 篇中多少有能夠幫忙到的部分。如有問題或想法也歡迎提出討論,謝謝!